/* 
 * File:   Rede.cpp
 * Author: Luciano
 * 
 * Created on 30 de Junho de 2011, 17:35
 */

#include <map>

#include "Rede.h"
#include "Bits.h"
#include "ConfigLoader.hpp"
#include "Transporte.h"
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <cstring>

using namespace utils;
using namespace std;

Rede::Rede() : Layer(0){
    this->configurarMapaDeIPs();
}

Rede::Rede(std::map<std::string,std::string>* conf) : Layer(0) {
    std::map<std::string, std::string>::iterator it = conf->begin();
    std::string tmp;
    for(; it != conf->end(); it++) {
        if(it->first.compare("souOGateway") == 0) {
            tmp = it->second;
            if(tmp.compare("true") == 0)
                souOGateway = true;
            else
                souOGateway = false;
        }
        else if(it->first.compare("meuIP") == 0) {
            tmp = it->second;
//            if(tmp != 0)
                meuIP = atoi(tmp.c_str());
        }
        else if(it->first.compare("mac") == 0) {
            tmp = it->second;
//            if(tmp != 0)
                meuMAC = atoi(tmp.c_str());
        }
        else if(it->first.compare("mapa") == 0) {
            tmp = it->second;
//            if(tmp != 0) {
                char p[100];
                strcpy(p, tmp.c_str());
                char *c = strtok(p, ",-");
                int ip,mac;
                int i = 0;
                while(c != NULL) {
                    if(i == 0) {
                        i++;
                        ip = atoi(c);
                    }
                    else {
                        i = 0;
                        mac = atoi(c);
                        mapaDeIPs.insert(std::pair<int,int>(ip,mac));
                    }
                    c = strtok(NULL, ",-");
                }
//            }
        }
    }
}

Rede::~Rede() {
}

void Rede::newReceivedMessage(Bits* dadosRecebidos) {
    cout << "Camada de rede - (IP:" << meuIP <<",MAC:" << meuMAC << ") recebeu a seguinte mensagem ";
    dadosRecebidos->println();
    int ipDestino = getIpDestinoRecebendo(dadosRecebidos);
    map<int, int>::iterator it;

    cout << "Camada de rede - IPDestino = " << ipDestino << endl;
    
    bool broadcast = dadosRecebidos->takeInfo(BROADCAST_OFFSET, BROADCAST_LENGTH);
    
    if(broadcast) {
        cout << "Camada de rede - E' Broadcast, envia para a camada de transporte e manda de volta pra enlace"<<endl;
        Bits* data = desmontaQuadro(dadosRecebidos);
        cout << "Camada de rede - Quadro desmontado: ";
        data->println();
        cout << "Camada de rede - Enviando para transporte"<<endl;
        getUpLayer()->receive(data);
        cout << "Camada de rede - Enviando para enlace"<<endl;
        getDownLayer()->send(dadosRecebidos);
    }

    //se o endereco e de alguem na minha rede
    else if( IPpertencenteARede( ipDestino ) ){
        cout << "Camada de rede - o IP "<<ipDestino<<" pertence a rede"<<endl;
        //se o pacote for para mim
        //manda pra camada de transporte
        if( ipDestino == meuIP ){
            cout << "Camada de rede - IP "<<ipDestino<<" eh o meu IP, manda pra camada de transporte"<<endl;
            Bits* data = desmontaQuadro( dadosRecebidos );
            delete dadosRecebidos;
            cout << "Camada de rede - Quadro desmontado: " << endl;;
            data->println();
            getUpLayer()->receive(data);
        }
        //se nao for pra mim
        //bota pra rodar ate que chegue nele
        else{
            cout << "Camada de rede - A mensagem nao eh pra mim. Manda pro proximo no da rede";
            int macParaEnviar = getMacParaEnviar();
            cout << "Camada de rede - MAC para enviar: "<<macParaEnviar<<endl;
            dadosRecebidos->putInfo(MAC_DESTINO_OFFSET, MAC_DESTINO_LENGTH, macParaEnviar);
            getDownLayer()->send( dadosRecebidos );
        }

    }
    //se o endereco nao for de ninguem da minha rede
    else{
        cout << "Camada de rede - O IP "<<ipDestino<<" nao pertence a rede"<<endl;
        //se eu sou o gateway
        if( souOGateway ){
            cout << "Camada de rede - Sou o gateway"<<endl;
            int macParaEnviar = macDestinoPadrao;
            cout << "Camada de rede - Enviar pro MAC da outra rede: "<<macParaEnviar<<endl;
            dadosRecebidos->putInfo(MAC_DESTINO_OFFSET, MAC_DESTINO_LENGTH, macParaEnviar);
            getDownLayer()->send( dadosRecebidos );
        }
        //se eu nao sou o gateway
        //boto pra rodar ate que chegue nele
        else{
            cout << "Camada de rede - Nao sou o gateway, passa pro proximo no, ate chegar no gateway"<<endl;
            int macParaEnviar = getMacParaEnviar();
            cout << "Camada de rede - MAC para enviar: "<<macParaEnviar<<endl;
            dadosRecebidos->putInfo(MAC_DESTINO_OFFSET, MAC_DESTINO_LENGTH, macParaEnviar);
            getDownLayer()->send( dadosRecebidos );
        }

    }
}

//Camada de transporte poe algo na fila, entao camada de rede tem nova msg pra enviar
void Rede::newMessageToSend(Bits* dadosAEnviar) {
    cout << "Camada de rede - (IP:" << meuIP <<",MAC:" << meuMAC << ") vai enviar a seguinte mensagem ";
    dadosAEnviar->println();
    int ipDestino = getIpDestinoEnviando(dadosAEnviar);
    std::map<int, int>::iterator it;
    bool broadcast;

    cout << "Camada de rede - IPDestino = "<<ipDestino<<endl;
    if(isBroadcast(dadosAEnviar)) {
        cout << "Camada de rede - E' broadcast"<<endl;
        broadcast = true;
        int macParaEnviar = getMacParaEnviar();
        cout << "Camada de rede - MAC para enviar: "<<macParaEnviar<<endl;
        Bits* data = montaQuadro(dadosAEnviar, macParaEnviar, broadcast);
        cout << "Camada de rede - Quadro montado ";
        data->println();
        delete dadosAEnviar;
        getDownLayer()->send( data );
    }
    else {
        cout << "Camada de rede - Nao e' broadcast"<<endl;
        broadcast = false;
        
        //casos de loopback
        //manda de volta pra camada de transporte
        if( ipDestino == meuIP ){
            cout << "Camada de rede - Enviando de volta para transporte, eh pra mim mesmo"<<endl;
            getUpLayer()->receive( dadosAEnviar );
        }        
        else{

            //se o IP de destino pertence a rede local
            //bota pra rodar ate que chegue nele
            if( IPpertencenteARede( ipDestino ) ){
                cout << "Camada de rede - IP "<<ipDestino<<" pertence a rede" << endl;
                int macParaEnviar = getMacParaEnviar();
                cout << "Camada de rede - MAC para enviar: " << macParaEnviar<<endl;
                Bits* data = montaQuadro(dadosAEnviar, macParaEnviar, broadcast);
                cout << "Camada de rede - Quadro montado ";
                data->println();
                delete dadosAEnviar;
                getDownLayer()->send( data );
            }
            //se nao pertence a rede local
            else{
                cout << "Camada de rede - IP "<<ipDestino<<" nao pertence a rede" << endl;
                //se eu sou o gateway
                //mando para o destino padrao
                if( souOGateway ){
                    cout << "Camada de rede - Sou o gateway"<<endl;
                    int macParaEnviar = macDestinoPadrao;
                    cout << "Camada de rede - Enviar pro MAC da outra rede: " << macParaEnviar;
                    Bits* data = montaQuadro(dadosAEnviar, macParaEnviar, broadcast);
                    cout << "Camada de rede - Quadro montado ";
                    data->println();
                    delete dadosAEnviar;
                    getDownLayer()->send( data );
                }
                //se eu nao sou o gateway
                //bota pra rodar ate que chegue nele
                else{
                    cout << "Camada de rede - Nao sou o gateway, passa pro proximo no, ate chegar no gateway"<<endl;
                    int macParaEnviar = getMacParaEnviar();
                    cout << "Camada de rede - MAC para enviar: " << macParaEnviar<<endl;
                    Bits* data = montaQuadro(dadosAEnviar, macParaEnviar, broadcast);
                    cout << "Camada de rede - Quadro montado ";
                    data->println();
                    delete dadosAEnviar;
                    getDownLayer()->send( data );
                }

            }

        }
    }

}

    Bits* Rede::montaQuadro(Bits* dadosAEnviar, int macParaEnviar, bool isBroadcast) {

        Bits* data = new Bits( dadosAEnviar->length() + CABECALHO_LENGTH );
        
        //Broadcast
        data->putInfo(BROADCAST_OFFSET, BROADCAST_LENGTH, isBroadcast);
        
        //MAC destino
        data->putInfo(MAC_DESTINO_OFFSET, MAC_DESTINO_LENGTH, macParaEnviar);
        
        //IP fonte (meu ip)
        data->putInfo(MEU_IP_OFFSET, MEU_IP_LENGTH, meuIP);

        //IP Destino, dados e cabecalho de transporte
        data->copyBits(dadosAEnviar, 0, dadosAEnviar->length(), CABECALHO_LENGTH);

        return data;

    }
    
    //formato do quadro de retorno: ipOrigem payload
    Bits* Rede::desmontaQuadro(Bits* dadosRecebidos) {
        Bits* data = new Bits( dadosRecebidos->length() - CABECALHO_LENGTH );

        int ipOrigem = getIpOrigemRecebendo( dadosRecebidos );

        data->putInfo(Transporte::IP_OFFSET, Transporte::IP_LENGTH, ipOrigem);
        
        //copia o payload
        //data->copyBits(dadosRecebidos, CABECALHO_LENGTH + Transporte.IP_LENGTH, 
        //                      dadosRecebidos->length() - (CABECALHO_LENGTH + Transporte.IP_LENGTH), Transporte.IP_LENGTH);
        data->copyBits(dadosRecebidos, CABECALHO_LENGTH + Transporte::IP_LENGTH,
        		dadosRecebidos->length() - (CABECALHO_LENGTH + Transporte::IP_LENGTH), Transporte::IP_LENGTH);

        return data;

    }
    
    
    //mapa de ips e macs..
    void Rede::configurarMapaDeIPs(){  
        cout << "Configurando maquina\n";
        ConfigLoader* conf = new ConfigLoader("cfg.txt");
        
        if(conf->getValue("souOGateway")->compare("true") == 0)
            souOGateway = true;
        else
            souOGateway = false;
        
        
        printf("%s o gateway\n",souOGateway ? "Eu sou" : "Nao sou");
        meuIP = atoi(conf->getValue("meuIP")->c_str());
        cout << "Meu IP: \n" << meuIP;
        meuMAC = atoi(conf->getValue("mac")->c_str());
        cout << "Meu MAC: \n" << meuMAC;
        
        char p[100];
        strcpy(p, conf->getValue("mapa")->c_str());
        char *tmp = strtok(p, ",-");
        int ip,mac;
        int i = 0;
        while(tmp != NULL) {
            if(i == 0) {
                i++;
                ip = atoi(tmp);
            }
            else {
                i = 0;
                mac = atoi(tmp);
                mapaDeIPs.insert(std::pair<int,int>(ip,mac));
            }
            tmp = strtok(NULL, ",-");
        }

        cout << "Mapa de IPs\n";
        std::map<int, int>::iterator it = mapaDeIPs.begin();
        for(int i = 1; it != mapaDeIPs.end(); i++,it++)
                printf("\tIP = %d, MAC = %d\n",it->first,it->second);
        
        macDestinoPadrao = 15;
        printf("MAC DestinoPadrao: %d\n",macDestinoPadrao);
        delete conf;
    }
    
    void Rede::leDoArquivo(std::string nomeDoArquivo){
        const char* arq = nomeDoArquivo.data();
        FILE* fp = fopen(arq,"r"); 
           if (fp==NULL){ 
               perror ("Erro ao abrir arquivo!");
               return;
           }           
           char strAux[100];
           char mapa[6];
           strcpy(mapa,"Mapa");       
           
           //Mapa 1 - 10, 2 - 11, 3 - 12
           
           while(true){
               
               fscanf(fp,"%s",strAux);//engole o mapa               
               if(strcmp(strAux,mapa)==0){    
                   while(true){
                       int ip, mac;
                       int n = fscanf(fp,"%d %s %d%s",&ip,strAux,&mac,strAux);  //1 -> 10,
                       mapaDeIPs.insert(std::pair<int,int>(ip,mac));
                       if(n < 4)
                           break;
                   }
                   
               }
               else
                   break;
                           
           }
           
           fclose(fp);     
    }
    
    //serve apenas com uma rede
    //na proxima versão, será necessário o IPDestino como parametro para fazer o Calculo bit a bt do mac
    int Rede::getMacParaEnviar() { 
        /*std::map<int, int>::iterator it;
        int ip = ( meuIP + 1 ) % (mapaDeIPs.size()+1);
        if(ip == 0)
            ip++;
        it = mapaDeIPs.find( ip );
        return it->second;*/
    	std::map<int, int>::iterator it = mapaDeIPs.find(meuIP);
    	it++;
    	if(it == mapaDeIPs.end())
    		it = mapaDeIPs.begin();
    	return it->second;

    }
    
    /**
     * Dado o IP, ele retorna o MAC correspondente ou -1 caso não exista o IP
     * @param IP
     * @return MAC ou -1 caso não encontre
     */
    int Rede::getMac(int IP) {
        std::map<int, int>::iterator it;
        it = mapaDeIPs.find(IP);
        if(mapaDeIPs.end() == it)
            return -1;
        else
            return it->second;
    }
        
    bool Rede::IPpertencenteARede(int ipDestino) {
        std::map<int, int>::iterator it = mapaDeIPs.find( ipDestino );
        if(mapaDeIPs.end() == it)
            return false;
        else
            return true;
//        int ipDest = it->second;
//        
//        return ( ipDest || ipDestino == meuIP );
    }
    
    bool Rede::isBroadcast(Bits* dados) {
        //0 tem que ser trocado por Transporte::IP_OFFSET e 4 por Transporte::IP_LENGTH
        for(int i = 0; i < 4; i++) {
            if(dados->get(i) == 0)
                return false;
        }
        
        return true;
    }

    int Rede::getIpDestinoEnviando(Bits* dadosAEnviar) {
        //return dadosAEnviar.takeInfo(Transporte.IP_OFFSET, Transporte.IP_LENGTH);
//        int inicioDoIP = 0, tamanhoDoIP = 4;    //codigo correto = linha acima, dependo do codigo de clistnes
        return dadosAEnviar->takeInfo(Transporte::IP_OFFSET, Transporte::IP_LENGTH);
    }
    
    int Rede::getIpOrigemRecebendo(Bits* dadosAEnviar) {
        return dadosAEnviar->takeInfo(MEU_IP_OFFSET, MEU_IP_LENGTH);
    }

    int Rede::getIpDestinoRecebendo(Bits* dadosRecebidos) {
        return dadosRecebidos->takeInfo(CABECALHO_LENGTH, MEU_IP_LENGTH);
    }

    std::map<int, int>* Rede::getMapaDeIPs() {
            return &mapaDeIPs;
    }

